﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq.Mapping;
using System.Runtime.Serialization;
using System.Transactions;
using System.Web;
using Portal.Core.Utils;
using Portal.Data.Helper;
using Portal.Data.Security;
using Portal.Core;
using Portal.Core.Binding.FormBuilder;
using Portal.Core.Helper;
using System.Linq;
namespace Portal.Data.Entity
{
    [Table(Name = "dbo.Category"), Serializable, DataContractAttribute]
    public class Category : PortalObject
    {
        #region Ctor
        public Category()
        {
            Status = Convert.ToInt32(ObjectStatusClass.CommonStatus.Active);
            CateType = Convert.ToInt32(ObjectTypeClass.ObjectType.News);
            CatLevel = 0;
        }
        #endregion

        #region Properties
        [JoinKey(Type = typeof(PortalObject))]
        public new long ID
        {
            get
            {
                return Category_ID;
            }
            set { Category_ID = value; }
        }

        [SkipBinding]
        [Column(IsPrimaryKey = true)]
        public long Category_ID { get; set; }

        [Column]
        public string Name { get; set; }

        [Column]
        public string FullName { get; set; }

        [Column, Lookup(DataSource = "FindAllForAdminInTree", DisplayField = "DisplayFullName", Type = typeof(Category))]
        public long Parent_ID { get; set; }

        [Column]
        public int CatOrder { get; set; }

        [SkipBinding]
        [Column]
        public int DetailPage_ID { get; set; }

        [SkipBinding]
        [Column]
        public int ListPage_ID { get; set; }

        [Column, SkipBinding]
        public int CatLevel { get; set; }

        [Column, Builder(DataType = DataType.Image), Image(SavePath = "Images/Category/")]
        public string Icon { get; set; }

        [Column, TextBoxMode(Mode = TextMode.MultipleLines)]
        public string Description { get; set; }

        [Column]
        public bool InvisibleInSubmenu { get; set; }

        [Column, Lookup(DataSource = "ListAll", DisplayField = "Name", Type = typeof(ObjectStatusClass))]
        public int Status { get; set; }

        [Column, Lookup(DataSource = "ListAll", DisplayField = "Name", Type = typeof(ObjectTypeClass))]
        public int CateType { get; set; }

        [Column]
        public string DomainCat { get; set; }

        #endregion

        #region extension props
        [SkipBinding]
        public string DisplayName
        {
            get
            {
                string prefixSpace = HttpUtility.HtmlDecode("&nbsp;&nbsp;");
                string prefixSubControl = HttpUtility.HtmlDecode("|---");
                for (int i = 0; i < CatLevel; i++)
                {
                    prefixSpace += prefixSpace;
                }
                return prefixSpace + prefixSubControl + Name;
            }
        }
        [SkipBinding]
        public string DisplayFullName
        {
            get
            {
                string prefixSpace = HttpUtility.HtmlDecode("&nbsp;&nbsp;");
                string prefixSubControl = HttpUtility.HtmlDecode("|---");
                for (int i = 0; i < CatLevel; i++)
                {
                    prefixSpace += prefixSpace;
                }
                var s = prefixSpace + prefixSubControl + FullName;
                return s;
            }
        }

        [SkipBinding]
        public string DisplayNameFullName
        {
            get
            {
                string prefixSpace = HttpUtility.HtmlDecode("&nbsp;&nbsp;");
                string prefixSubControl = HttpUtility.HtmlDecode("|---");
                for (int i = 0; i < CatLevel; i++)
                {
                    prefixSpace += prefixSpace;
                }
                var s = prefixSpace + prefixSubControl + FullName + " (" + Name + ")";
                return s;
            }
        }

        public string Url
        {
            get { return "/" + Name + CoreContext.UrlExtension; }
        }
        #endregion

        #region Methods
        public new static Category FindByPrimaryKey(object objectid)
        {
            return FindByPrimaryKey<Category>(objectid);
        }

        public Category Parent()
        {
            return FindByPrimaryKey(Parent_ID);
        }

        public IList Categories()
        {
            return FindSubCategories(ID);
        }

        /// <summary>
        /// Get childrens in one lower level.
        /// </summary>
        /// <returns></returns>
        public static IList FindSubCategories(Int64 parentId, bool renewCaching = false)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindSubCategories", parentId };
//            if (!renewCaching)
//            {
//                var obj = CacheHelper.Instance.Get(parameter);
//                if (obj != null)
//                    return (IList)obj;
//            }
            //end get cache -------------------
            using (var context = new CmsDataContext())
            {
                var results = context.Categories.Where(o => o.Parent_ID == parentId).OrderBy(o => o.CatOrder).ToList();
                CacheHelper.Instance.Set(parameter, results);
                return results;
            }
        }

        /// <summary>
        /// Get all child category, include input category is default
        /// </summary>
        /// <returns></returns>
        public static IList FindSubCategoriesInTree(Category root, bool renewCaching = false)
        {
            if (root == null)
                return new ArrayList();

            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindSubCategoriesInTree", root.ID };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                    return (IList)obj;
            }
            //end get cache -------------------
            var results = new ArrayList { root };
            var subs = FindSubCategories(root.ID);
            if (subs.Count > 0)
            {
                foreach (Category child in subs)
                {
                    results.AddRange(FindSubCategoriesInTree(child));
                }
            }
            CacheHelper.Instance.Set(parameter, results);
            return results;
        }

        /// <summary>
        /// Get only category roots
        /// </summary>
        /// <returns></returns>
        public static IList FindRoots()
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindRoots" };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (IList)obj;
            //end get cache -------------------
            IList result;
            using (var context = new CmsDataContext())
            {
                result = context.Categories.Where(o => o.CatLevel == 0).OrderBy(o => o.CatOrder).ToList();
            }
            CacheHelper.Instance.Set(parameter, result);
            return result;
        }

        /// <summary>
        /// Get tree category, each root has a tree.
        /// </summary>
        /// <returns></returns>
        public static IList ListAll()
        {
            var trees = new ArrayList();
            var roots = FindRoots();

            if (roots.Count == 0) return null;

            foreach (Category root in roots)
            {
                var tree = FindSubCategoriesInTree(root);
                trees.AddRange(tree);
            }
            return trees;
        }

        /// <summary>
        /// Get list category, special add a NULL category in the first.
        /// </summary>
        /// <returns></returns>
        public static object FindAllForAdminInTree()
        {
            //create null for ROOT
            var catNull = new Category { FullName = "This category is virtual", Name = "[parent of root]" };

            var trees = new ArrayList { catNull };

            var roots = FindRoots();
            foreach (Category root in roots)
            {
                var tree = FindSubCategoriesInTree(root);
                trees.AddRange(tree);
            }
            return trees;
        }

        /// <summary>
        /// Get list parent, include current category
        /// </summary>
        public static IList FindParentsOnly(Category currCategory)
        {
            if (currCategory == null)
                return null;

            //getcache  -----------------------

            var parameter = new object[] { typeof(Category).Name, "FindParentsOnly", currCategory.ID };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (IList)obj;
            //end get cache -------------------

            var results = new ArrayList();
            var parent = currCategory.Parent();

            //first insert
            results.Add(currCategory);

            while (parent != null)
            {
                results.Insert(0, parent);
                parent = parent.Parent();
            }
            CacheHelper.Instance.Set(parameter, results);
            return results;
        }

        /// <summary>
        /// Get the first in list roots
        /// </summary>
        /// <returns></returns>
        public static Category FindFirstRoot()
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindFirstRoot" };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (Category)obj;
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var result = context.Categories.Where(o => o.CatLevel == 0).OrderBy(o => o.CatOrder).FirstOrDefault();
                CacheHelper.Instance.Set(parameter, result);
                return result;
            }
        }

        /// <summary>
        /// Get category by Name
        /// </summary>
        public static Category FindByName(string name, bool renewCaching = false)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindByName", name };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                    return (Category)obj;
            }
            //end get cache -------------------
            using (var context = new CmsDataContext())
            {
                var result = context.Categories.FirstOrDefault(o => o.Name == name);
                CacheHelper.Instance.Set(parameter, result);
                return result;
            }
        }

        public static List<Category> FindByType(ObjectTypeClass.ObjectType type, bool renewCaching = false)
        {
            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindByType", (int)type };
            if (!renewCaching)
            {
                var obj = CacheHelper.Instance.Get(parameter);
                if (obj != null)
                    return (List<Category>)obj;
            }
            //end get cache -------------------

            using (var context = new CmsDataContext())
            {
                var result = context.Categories.Where(o => o.CateType == (int)type).OrderBy(o => o.CatOrder).ToList();
                CacheHelper.Instance.Set(parameter, result);
                return result;
            }
        }

        public static IList FindByNewsType()
        {
            return FindByType(ObjectTypeClass.ObjectType.News);
        }

        public static List<Category> FindByNames(string[] names)
        {
            //getcache  -----------------------

            var parameter = new object[] { typeof(Category).Name, "FindByNames", names };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Category>)obj;
            //end get cache -------------------

            var list = names.Select(t => FindByName(t.Trim())).Where(cat => cat != null).ToList();
            CacheHelper.Instance.Set(parameter, list);
            return list;
        }

        public static List<Category> FindByIds(string sId)
        {
            if (string.IsNullOrEmpty(sId))
                return new List<Category>();

            //getcache  -----------------------

            var parameter = new object[] { typeof(Category).Name, "FindByIds", sId };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (List<Category>)obj;
            //end get cache -------------------

            string[] strArr = sId.Split(',');
            Int64[] intArr = Array.ConvertAll(strArr, Convert.ToInt64);
            using (var context = new CmsDataContext())
            {
                var result = context.Categories.Where(o => intArr.Contains(o.Category_ID)).ToList();
                CacheHelper.Instance.Set(parameter, result);
                return result;
            }
        }

        public static Category FindFirstByWebpartId(int webpartId)
        {
            //getcache  -----------------------

            var parameter = new object[] { typeof(Category).Name, "FindFirstByWebpartId", webpartId };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (Category)obj;
            //end get cache -------------------
            using (var context = new CmsDataContext())
            {
                var result =
                     context.Categories.FirstOrDefault(o => o.ListPage_ID == webpartId || o.DetailPage_ID == webpartId);
                CacheHelper.Instance.Set(parameter, result);
                return result;
            }
        }

        public static IList FindByBook(int bookId)
        {
            if (bookId <= 0)
                return new ArrayList();

            //getcache  -----------------------
            var parameter = new object[] { typeof(Category).Name, "FindByBook", bookId };
            var obj = CacheHelper.Instance.Get(parameter);
            if (obj != null)
                return (IList)obj;
            //end get cache -------------------

            var sql = "select c.* from Category c INNER JOIN BookCategory oc ON oc.Category_ID=c.Category_ID where oc.Book_ID={0}";
            sql += " order by c.CatOrder";
            var result = LINQHelper.GetList<Category>(sql, bookId);
            CacheHelper.Instance.Set(parameter, result);
            return result;
        }
        #endregion

        #region Logic

        /// <summary>
        /// Check category in the left side is the Parent (or deeper) of right side.
        /// </summary>
        /// <returns></returns>
        public static bool IsMatchParent(Int64 parentId, Category subCategory)
        {
            var parents = FindParentsOnly(subCategory);
            return parents.Cast<Category>().Any(pr => pr.Category_ID == parentId);
        }

        public static string BuildListId(IList categories)
        {
            if (categories == null || categories.Count == 0) return "";

            string result = categories.Cast<Category>().Aggregate("", (current, cat) => current + (cat.Category_ID + ","));
            result = result.Remove(result.Length - 1);
            return result;
        }
        public static string BuildListName(IList categories)
        {
            if (categories == null || categories.Count == 0) return "";
            string result = categories.Cast<Category>().Aggregate("", (current, cat) => current + (cat.FullName + ","));
            result = result.Remove(result.Length - 1);
            return result;
        }

        /// <summary>
        /// Get split of name from list category
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="isLink"></param>
        /// <returns></returns>
        public static string SplitCategoryFromList(object obj, bool isLink = true)
        {
            var categories = (IList)obj;
            string names = "";
            if (categories.Count > 0)
            {
                string link = (isLink) ? "<a target=_blank href=\"{0}\">{1}</a> | " : "{1} | ";
                names = categories.Cast<Category>().Aggregate(names, (current, cat) => current + string.Format(link, cat.Url, cat.FullName));
                names = names.Substring(0, names.Length - 2);
            }
            return names;
        }

        public object Clone()
        {
            return MemberwiseClone();
        }
        public static IList Clone(IList value)
        {
            IList clones = new ArrayList();
            foreach (Category item in value)
            {
                clones.Add(item.Clone());
            }
            return clones;
        }

        public bool ContainIn(IList list)
        {
            return list.Cast<Category>().Any(cat => cat.Category_ID == Category_ID);
        }
        public static Category CreateNew(Int64 parentId, string fullName = "New Category")
        {
            var parent = FindByPrimaryKey(parentId);
            if (parent != null)
            {
                var newcat = new Category
                                 {
                                     Parent_ID = parentId,
                                     FullName = fullName,
                                     Name = Guid.NewGuid().ToString().Substring(0, 8),
                                     ListPage_ID = WebpartPage.CreateNewPage().ID,
                                     DetailPage_ID = WebpartPage.CreateNewPage().ID,
                                     CatLevel = parent.CatLevel + 1
                                 };
                newcat.Save();
                return newcat;
            }
            return null;
        }
        bool NameExisted(string name, long catId)
        {
            using (var context = new CmsDataContext())
            {
                return context.Categories.Count(x => x.Name.ToLower() == name.ToLower() && x.Category_ID != catId) > 0;
            }
        }
        public override void BeforeSaveUpdate()
        {
            base.BeforeSaveUpdate();
            var parent = Parent();
            if (parent != null)
                CatLevel = parent.CatLevel + 1;
            if (string.IsNullOrEmpty(Name))
            {
                Name = GetSafeName(FullName);
            }
            if (NameExisted(Name, ID))
            {
                int i = 1;
                var arr = Name.Split('-');
                if (arr.Length > 1 && MyCheck.IsInt(arr[arr.Length - 1]))
                {
                    i = Convert.ToInt32(arr[arr.Length - 1]) + 1;
                    int lastcut = Name.LastIndexOf('-');
                    Name = Name.Substring(0, lastcut);
                }

                const string add = "-{0}";
                while (true)
                {
                    if (!NameExisted(Name + string.Format(add, i), ID))
                    {
                        Name += string.Format(add, i);
                        break;
                    }
                    i++;
                }
            }
        }

        public override void AfterSaveUpdate()
        {
            base.AfterSaveUpdate();
            Recache();
        }

        public override void BeforeDelete()
        {
            base.BeforeDelete();
            //need del all sub before del this parent
            var childs = FindSubCategories(ID);
            foreach (Category child in childs)
            {
                child.Delete();
            }
        }
        public override void AfterDelete()
        {
            base.AfterDelete();
            //recache
            var parameter = new object[] { typeof(Category).Name, "FindRoots" };
            CacheHelper.Instance.Set(parameter, null);
            if (Parent_ID > 0)
            {
                FindSubCategoriesInTree(this, true);
                FindSubCategories(Parent_ID, true);
            }
        }
        void Recache()
        {
            FindSubCategoriesInTree(this, true);
            FindSubCategories(ID, true);
            if (Parent_ID > 0)
            {
                FindSubCategoriesInTree(Parent(), true);
                FindSubCategories(Parent_ID, true);
            }
            FindByName(Name, true);
            var parameter = new object[] { typeof(Category).Name, "FindRoots" };
            CacheHelper.Instance.Set(parameter, null);
        }
        #endregion
    }
}